package org.fjsei.yewu.aop.hibernate;

import org.fjsei.yewu.filter.Node;
import org.hibernate.search.engine.backend.document.DocumentElement;
import org.hibernate.search.engine.backend.document.IndexFieldReference;
import org.hibernate.search.engine.backend.document.IndexObjectFieldReference;
import org.hibernate.search.engine.backend.document.model.dsl.IndexSchemaObjectField;
import org.hibernate.search.engine.backend.types.Aggregable;
import org.hibernate.search.engine.backend.types.IndexFieldType;
import org.hibernate.search.engine.backend.types.Projectable;
import org.hibernate.search.engine.backend.types.Sortable;
import org.hibernate.search.mapper.pojo.bridge.PropertyBridge;
import org.hibernate.search.mapper.pojo.bridge.binding.PropertyBindingContext;
import org.hibernate.search.mapper.pojo.bridge.mapping.programmatic.PropertyBinder;
import org.hibernate.search.mapper.pojo.bridge.runtime.PropertyBridgeWriteContext;
import org.hibernate.search.mapper.pojo.model.PojoModelProperty;


/** 只需要索引关联对象的ID字段，代替掉@IndexedEmbedded(includeEmbeddedObjectId=true,includePaths={"id"} )
 * 目标是提高性能：不要多余的sql查询语句，没必要查询每一个具体关联对象表。
 * 代替掉 @IndexedEmbedded(includePaths = {"id"} )
 * 对象属性桥接 hibernateSearch；
 * 自动重建索引时刻，发现关联查询没啥用处的查了很多字段。如何能够避免，提高性能！
 * 相应字段的@ManyToOne(fetch = FetchType.LAZY) 还需要配合LAZY才能发挥出优点。
 * 估计是：OneToOne,ManyToOne的属性才能体现出优越性的；若是OneToMany,ManyToMany的是第三张中间表的List<?>字段估计无法避免联合查询=类似这里的做法就没法提升性能。
 * */
public class NodeIdBinder implements PropertyBinder {
    //目标索引字段名。默认，下面替换为注解的实际字段名。
    private String fieldName = "object";

    public NodeIdBinder fieldName(String fieldName) {
        this.fieldName = fieldName;
        return this;
    }

    @Override
    public void bind(PropertyBindingContext context) {
        //确保关联属性被修改时，涉及ES索引也能够修改。
        context.dependencies()
                .use( "id" );

//        IndexSchemaObjectField embedField = context.indexSchemaElement()
//                .objectField( this.fieldName );

        PojoModelProperty bridgedElement = context.bridgedElement();
        IndexSchemaObjectField embedField = context.indexSchemaElement()
                .objectField( bridgedElement.name() );

        PojoModelProperty  idProperty= bridgedElement.property("id");
        //类型不一致报错：field 'ispu.id': Invalid value. Expected 'keyword', actual is 'long'
        boolean isLongType= idProperty.isAssignableTo(Long.class);        //boolean isUUID= idProperty.isAssignableTo(UUID.class);

        /*修改注解以后：启动时刻Hibernate Search检查旧的ES索引的字段定义和新的要求不一致，导致启动报错！只能刪除旧索引！
         field 'dev.id': attribute 'doc_values': - Invalid value. Expected 'true', actual is 'false'；
         默认产生是"doc_values": false；不⽀持排序，需要doc_values=true才⾏；加.aggregable(Aggregable.YES).sortable(Sortable.YES)对应于doc_values=true;
         doc_values=true 时，ES 会增加一个相应的正排索引。 #假如Long ID字段在ES是可以转为"type": "long"存储的。
        两种情况：NodeIdBinder自动适应Long和UUID 类型的ID； ES存储不同类型。
        * */
        if(isLongType){
            IndexFieldType<Long> longidFieldType = context.typeFactory().asLong()
                    .aggregable(Aggregable.YES).sortable(Sortable.YES)
                    .projectable(Projectable.YES)
                    .toIndexFieldType();

            context.bridge(Node.class, new LongBridge(
                    embedField.toReference(),
                    //可以加多个： 对象字段有下面嵌套的有多个关联的字段
                    embedField.field("id", longidFieldType).toReference()
            ));
        }
        else {
            IndexFieldType<String> uuidFieldType = context.typeFactory().asString()
                    .aggregable(Aggregable.YES).sortable(Sortable.YES)
                    .projectable(Projectable.YES)
                    .toIndexFieldType();

            context.bridge(Node.class, new Bridge(
                    embedField.toReference(),
                    //可以加多个： 对象字段有下面嵌套的有多个关联的字段
                    embedField.field("id", uuidFieldType).toReference()
            ));
        }
    }
/*若是输出索引对象字段有下面嵌套的有多个关联的字段：需要上面和下面配套多个参数的。
private Bridge(IndexObjectFieldReference embedField,
                IndexFieldReference<BigDecimal> uuidField,
                IndexFieldReference<BigDecimal> booksField)
底下的implements PropertyBridge<Uunode> { 这里的泛型<Uunode>是依据于被注解实体字段的类型的。
* */
    @SuppressWarnings("rawtypes")
    private static class Bridge implements PropertyBridge<Node> {

        private final IndexObjectFieldReference embedField;       //关联对象字段本身
        private final IndexFieldReference<String> uuidField;       //关联对象字段底下的多个字段
        //private final IndexFieldReference<String> booksField;
        private Bridge(IndexObjectFieldReference embedField,
                       IndexFieldReference<String> uuidField) {
            this.embedField = embedField;
            this.uuidField = uuidField;
        }
        /*针对是关联对象且 fetch= FetchType.LAZY)的才有意义的。Uunode就不会自动查询关联对象了。
        bridgedElement的类型: ID类型是UUID,在ES索引库存储都会变为字符串。 #Enum类型-数据库存储是Number类型, ES却是Keyword字符串的类型，
        Long类型ID的话，ES库可以用 long 做存储。
        * */
        @Override
        public void write(DocumentElement target, Node bridgedElement, PropertyBridgeWriteContext context) {
            //Session session = context.extension( HibernateOrmExtension.get() ).session();
            //@SuppressWarnings("unchecked")
            //Node lineItems = (Node) bridgedElement;   这里最为关键：类型决定了如何提取，是否需要查询关联数据库表。节省JPA关联实体load的必要性！但是断点单步调试的会主动加载部分。
            Object id= null!=bridgedElement? bridgedElement.getId() : null;
            if(null!=id) {
                DocumentElement top = target.addObject( this.embedField );
                top.addValue(this.uuidField, id.toString());
            }
        }

    }

    private static class LongBridge implements PropertyBridge<Node> {

        private final IndexObjectFieldReference embedField;       //关联对象字段本身
        private final IndexFieldReference<Long> longidField;       //关联对象字段底下的多个字段
        //private final IndexFieldReference<String> booksField;
        private LongBridge(IndexObjectFieldReference embedField,
                           IndexFieldReference<Long> longidField) {
            this.embedField = embedField;
            this.longidField = longidField;
        }
        /*针对是关联对象且 fetch= FetchType.LAZY)的才有意义的。Uunode就不会自动查询关联对象了。
        bridgedElement的类型: ID类型是UUID,在ES索引库存储都会变为字符串。 #Enum类型-数据库存储是Number类型, ES却是Keyword字符串的类型，
        Long类型ID的话，ES库可以用 long 做存储。
        * */
        @Override
        public void write(DocumentElement target, Node bridgedElement, PropertyBridgeWriteContext context) {
            //Session session = context.extension( HibernateOrmExtension.get() ).session();
            //@SuppressWarnings("unchecked")
            //Node lineItems = (Node) bridgedElement;   这里最为关键：类型决定了如何提取，是否需要查询关联数据库表。节省JPA关联实体load的必要性！但是断点单步调试的会主动加载部分。
            Object id= null!=bridgedElement? bridgedElement.getId() : null;
            if(null!=id) {
                DocumentElement top = target.addObject( this.embedField );
                //和上面的top.addValue(this.uuidField, id.toString()) #不一样函数！
                top.addValue(this.longidField, (Long)id );
            }
        }

    }
}

